/*
 * Winfo
 * by Paul S. Kleppner
 *
 * This program may be freely distributed, but not sold.
 * It is provided without warranty of any kind, expressed or
 * implied, as to its fitness for any particular use.
 */

/*
 * WinInfo class.  This is the controlling object of the
 * application -- it manages the report panel, and does
 * all the work.
 */

#import "WinInfo.h"
#import <appkit/Application.h>
#import <appkit/View.h>
#import <appkit/Window.h>
#import <appkit/Panel.h>
#import <appkit/Control.h>
#import <appkit/Font.h>
#include <dpsclient/wraps.h>
#include "wwrap.h"
#include <stdlib.h>
#import <appkit/timer.h>
#import <appkit/Matrix.h>

#import "WView.h"
#import "WinApp.h"


@implementation WinInfo

+ new
{
  self = [super new];
  windowList = NULL;
  wpList = NULL;
  onScreenList = NULL;
  displayWin = nil;
  activeFlag = NO;

  myFont = [Font newFont:"Helvetica" size:10.0 style:0
		  matrix:NX_IDENTITYMATRIX];
  if( [myFont screenFont] )
	myFont = [myFont screenFont];

  [NXApp setDelegate:self];

  outlineMode = YES;
  offScreenMode = NO;

  return self;
}

// target of the start/stop button.
- startStop:sender
{
  activeFlag = !activeFlag;

  if( activeFlag ) 
	[self start:sender];
  else
	[self stop:sender];

  [timeDelay setEnabled:!activeFlag];

  return self;
}

// Start things going.  Messaged when the user
// has presed the "start" button.
// sender is the start/stop button.
- start:sender
{
  id vw;
  NXRect r;
  struct winfo *wp;
  int i;

  int secs, osecs;
  NXTrackingTimer myTimer;

  selectedContext = -1;

  // Delay, if user requested
  osecs = secs = [timeDelay intValue];

  if( secs > 0 ){
	NXBeginTimer(&myTimer, 0.0, 1.0);
	for( ; secs > 0; --secs ){
	  [NXApp getNextEvent:NX_TIMERMASK];
	  [timeDelay setIntValue:secs-1];
	  NXPing();
	}
	NXEndTimer(&myTimer);
  }

  [self getHighlightValues:highlightOpt];
  [self clearReport];

  // Get information on all windows on the system.
  [self buildWindowList];

  // And get numbers of on-screen windows.
  [self buildOnScreenList];

  // Look through the on-screen list, and set the winfo struct
  // to record that each of these windows is on screen.
  for( i = 0; i < numOnScreen; ++i ){
	wp = [self findWinfoByNumber:onScreenList[i]];
	if( wp != NULL )
	  wp->origOnScreen = wp->onScreenNow = YES;
  }

  // Set up displayWin.  This is a window the size of the full screen,
  // into which we draw the outlines or contents of all other windows.
  // displayWin gets set in front of all other windows on the system
  // (except the Winfo report panel), and hides them all.
  // displayWin is a panel, so that we can avoid making it key
  [NXApp getScreenSize:&r.size];
  displayWin = [Panel newContent:&r style:NX_PLAINSTYLE backing:NX_BUFFERED
		buttonMask:0 defer:NO];
  [displayWin setBecomeKeyOnlyIfNeeded:YES];		// it will never be key
  vw = [WView newForClient:self];
  
  [displayWin setContentView:vw];

  // Put the report panel at the top-most level
  [sender lockFocus];
  mySetCurrentWindowLevel(NX_MAINMENULEVEL+2);
  [[sender window] orderFront:self];
  [sender unlockFocus];

  // Fill in display window with images of all other windows
  [self showAllWindows];

  // Put the display window one level beneath the report panel
  // (but in front of everybody else)
  [vw lockFocus];
  mySetCurrentWindowLevel(NX_MAINMENULEVEL+1);
  [vw unlockFocus];

  [displayWin orderFront:self];

  [timeDelay setIntValue:osecs];

  // If there was a timer, we may not be active at this point;
  // so activate us.
  [NXApp activateSelf:YES];

  NXPing();

  return self;
}

// Called when the stop button is pressed.  sender is that button.
// Free up data structures, and the displayWin window.

- stop:sender
{
  [self clearOffScreen];

  if( windowList != NULL )
	free(windowList);
  if( wpList != NULL )
	free(wpList);
  [displayWin free];
  windowList = NULL;
  wpList = NULL;
  displayWin = nil;

  [[sender window] orderFront:self];
  mySetCurrentWindowLevel(NX_NORMALLEVEL);

  return self;
}

// Mouse down proxy.  Called by the WView when a mouse down occurs
// on the full-screen display window.  WView masks all other
// windows (except the Winfo report panel), so it receives all mouse hits.
// We figure out what window was at that point, and bring that window
// to front (or send it to back if a command-click).  Then we
// update the report panel appropriately.
- mouseDownProxy:(NXEvent *)event
{
  struct winfo *wp;

  // Get the winfo for the window at the mouse-hit.
  wp = [self mapPointToWindow:&event->location];

  if( wp != NULL ){
	if( event->flags & NX_COMMANDMASK ){
	  // command-click: send to back
	  myOrderWindow(NX_BELOW, 0, wp->num);
	} else {
	  // regular click: send to front and select
	  if( offScreenMode ){
		// If the selected context changes, clear old context's off-
		// screen windows, and show new ones
		if( wp->context != selectedContext ){
		  [self moveOnScreen:NO forContext:selectedContext];
		  [self moveOnScreen:YES forContext:wp->context];
		}
	  }
	  selectedContext = wp->context;
	  myOrderWindow(NX_ABOVE, 0, wp->num);
	  // Update report info for windows
	  [self showReportFor:wp];
	}

	// Redisplay image of all windows, to reflect new order
	[self showAllWindows];
  }
  return self;
}


// showAllWindows.  This contructs an image of all windows
// on the displayWin which hides them all.  If outlineMode is set,
// then we just draw outlines; otherwise, we fill in the images.
- showAllWindows
{
  struct winfo *wp;
  NXRect r;
  int i;

  [[displayWin contentView] lockFocus];
  [myFont set];

  [[displayWin contentView] getBounds:&r];

  if( outlineMode ){
	// clear background to dark gray
	PSsetgray(NX_DKGRAY);
	NXRectFill(&r);

	[self buildOnScreenList];
	for( i = numOnScreen-1; i >= 0; --i ){
	  if( wp = [self findWinfoByNumber:onScreenList[i]] ){
		if( !wp->isMe )
		  [self showOutlineWindow:wp markBorder:
		   (wp->context == selectedContext)];
	  }
	}
  } else {
	fillBelowWin([self trueWinNumberFor:reportPanel],
				 [self trueWinNumberFor:displayWin]);
  }

  [[displayWin contentView] unlockFocus];

  [displayWin flushWindow];
  NXPing();

  return self;
}

// Draw the outline for the given window.  If markBorder is
// YES, then draw the border with a heavy black line.
- showOutlineWindow:(struct winfo *)wp markBorder:(BOOL)mark
{
  BOOL highlight;
  char cbuf[128];
  
  PSsetgray(NX_BLACK);

  highlight = NO;

  if( highlightAlpha && wp->hasAlpha )
	highlight = YES;

  if( highlightColor && wp->colors > 1 )
	highlight = YES;

  // Don't highlight depth for color windows, unless highlightColor is set
  if( highlightDepth && wp->depth > 2 && (highlightColor || wp->colors == 1) )
	highlight = YES;

  PSsetgray(highlight ? (NX_LTGRAY+NX_WHITE)/2 : NX_WHITE );
  NXRectFill(&wp->place);
	
  PSsetgray(NX_BLACK);
  NXFrameRectWithWidth(&wp->place, mark ? 4.0 : 1.0);

  PSmoveto(wp->place.origin.x + 4, 
		   wp->place.origin.y + wp->place.size.height - 12);
  sprintf(cbuf, "%.1fkb", ((float) [self sizeBackingStore:wp]) / 1024);
  PSshow(cbuf);
  
  return self;
}

// Given a point, find the winfo struct for the top-most window at
// that point.  Returns NULL if not found.
- (struct winfo *) mapPointToWindow:(NXPoint *)p
{
  int wnum;
  float wx, wy;
  int found;

  myFindWindow(p->x, p->y, NX_BELOW, [self trueWinNumberFor:displayWin],
			   &wx, &wy, &wnum, &found);
  if( found )
	return [self findWinfoByNumber:wnum];
  else
	return NULL;
}

// Target of the highlight check boxes.  If we are active, then
// redisplay the windows so that the new highlight options area active.
- changeHighlight:sender
{
  if( activeFlag ){
	[self getHighlightValues:sender];
	[self showAllWindows];
  }
  return self;
}

// Get the user's highlight choices by interrogating the highlight 
// boxes.  sender is a matrix of check boxes
- getHighlightValues:sender
{
  highlightAlpha = ([[sender findCellWithTag:0] intValue] > 0 );
  highlightColor = ([[sender findCellWithTag:1] intValue] > 0 );
  highlightDepth = ([[sender findCellWithTag:2] intValue] > 0 );
  return self;
}

// Clear the report panel
- clearReport
{
  [repBox setTitle:"No Window Selected"];
  [repBox display];

  [repContext setStringValue:""];
  [repSize setStringValue:""];
  [repAlpha setStringValue:""];
  [repDepth setStringValue:""];
  [repColors setStringValue:""];
  [repBackingStore setStringValue:""];
  [repOnScreen setStringValue:""];
  [repOffScreen setStringValue:""];
  [repTotalScreen setStringValue:""];
  [repTotalBackingStore setStringValue:""];

  return self;
}

// Display in the report panel a report for the 
// window with the given winfo struct.
- showReportFor:(struct winfo *)wp
{
  NXRect *rp;
  char cbuf[128];
  int i, tback, non, noff;
  struct winfo *wp2;
  
  rp = &wp->place;

  // Set the selected report box title to specify this window's number
  sprintf(cbuf, "Window #%d", wp->num);
  [repBox setTitle:cbuf];
  [repBox display];

  // Show context
  sprintf(cbuf, "0x%x", wp->context);	
  [repContext setStringValue:cbuf];

  // Show size
  sprintf(cbuf, "%.0f x %.0f", rp->size.width, rp->size.height);
  [repSize setStringValue:cbuf];

  // Show alpha
  if( wp->hasAlpha )
	[repAlpha setStringValue:"Yes"];
  else
	[repAlpha setStringValue:"No"];

  // Show depth and colors
  [repDepth setIntValue:wp->depth];
  [repColors setIntValue:wp->colors];

  // Show backing store
  [repBackingStore setIntValue:[self sizeBackingStore:wp]];


  // For all windows in same context, count number
  // on and off-screen, and total their backing store

  non = noff = 0;
  tback = 0;

  for( i = 0; i < numWins; ++i ){
	wp2 = wpList + i;
	if( wp2->context == wp->context ){
	  if( wp2->origOnScreen )
		++non;
	  else
		++noff;
	  tback += [self sizeBackingStore:wp2];
	}
  }

  // Report on totals for windows in same context
  [repOnScreen setIntValue:non];
  [repOffScreen setIntValue:noff];
  [repTotalScreen setIntValue:(non+noff)];
  [repTotalBackingStore setIntValue:tback];
  
  return self;
}

// Calculate the backing store used for the given window.
// The calculation is based on: window size; number of colors;
// depth of each color; and alpha.  It does not include
// any other overhead or rounding within the window server.
- (int) sizeBackingStore:(struct winfo *)wp
{
  NXRect *rp;
  int depth;

  depth = wp->depth * wp->colors;
  rp = &wp->place;

  // If alpha is set, then we guess the number of alpha bits based on
  // the depth.  (There are two bits alpha for a depth of two, 4 bits
  // for a depth of 12, and 8 bits for a depth of 24.)
  if( wp->hasAlpha ){
	if( depth == 2 )
	  depth = 4;
	else
	  depth *= (4/3);
  }

  return (int) ((rp->size.width * rp->size.height * depth)/8);
}

// The appkit assigns logical window number to each window, apparantly
// for its convenience.  This function returns the "true" window
// number assigned by the window server, given an Appkig window number.
- (int) trueWinNumberFor:win
{
  int n;
  id vw;

  vw = [win contentView];
  [vw lockFocus];
  PScurrentwindow(&n);
  [vw unlockFocus];
  return n;
}

// Target of the "display window contents" check box.
// If set, we display the full window contents; if not, we
// show only outlines.
- showOutlines:sender
{
  outlineMode = ([sender intValue] == 0);

  if( activeFlag )
	[self showAllWindows];

  return self;
}


// Target of the "show off-screen windows" check box.
// If set, we show all off-screen windows; else we show
// only on-screen windows.
- showOffScreen:sender
{
  offScreenMode = ([sender intValue] == 1 );

  if( activeFlag ){
	[self moveOnScreen:offScreenMode forContext:selectedContext];

	[self showAllWindows];
  }

  return self;
}

- clearOffScreen
{
  [self moveOnScreen:NO forContext:selectedContext];
  [self showAllWindows];

  [showOffScreenButton setEnabled:YES];
  return self;
}

// how == YES to move off-screen windows on, else moves them back off
// context is what context's windows to change
- moveOnScreen:(BOOL)how forContext:(int)context
{
  int i;
  struct winfo *wp;

  for( i = 0; i < numWins; ++i ){
	wp = wpList + i;

	if( (wp->context == context) && !wp->origOnScreen ){
	  if( how ){
		if( !wp->onScreenNow ){
		  myOrderWindow(NX_ABOVE, 0, wp->num);
		  wp->onScreenNow = YES;
		}
	  } else {
		if( wp->onScreenNow ){
		  myOrderWindow(NX_OUT, 0, wp->num);
		  wp->onScreenNow = NO;
		}
	  }
	}
  }
  return self;
}

- appWillTerminate:sender
{
  if( wpList != NULL )
	[self clearOffScreen];

  return self;
}

// Given a DPS window number, return the winfo struct that
// describes that window.  We just do a linear scan; this is fine
// given the relatively small number of windows we expect.
- (struct winfo *) findWinfoByNumber:(int)wnum
{
  struct winfo *wp;
  int i;

  wp = wpList;
  for( wp = wpList, i = numWins; i > 0; --i, ++wp )
	if( wp->num == wnum )
	  return wp;
  return NULL;
}

// Build the complete window list.  This will calculate numWins,
// create wpList, and fill in a wpList with info on all existing windows.
- buildWindowList
{
  int i;
  int alpha, depth;
  struct winfo *wp;
  int myContext;


  myContext = [NXApp contextNumber];

  // Get an array of DPS window numbers
  PScountwindowlist(0, &numWins);
  windowList = malloc(sizeof(int) * numWins);
  PSwindowlist(0, numWins, windowList);

  wpList = malloc(sizeof(struct winfo) * numWins);

  --numWins;					// skip back-most window

  // For each window, get info for winfo struct describing window
  for( i = 0; i < numWins; ++i ){
	wp = wpList + i;
	wp->num = windowList[i];
	myCurrentWindowBounds(windowList[i],
						  &wp->place.origin.x, &wp->place.origin.y,
						  &wp->place.size.width, &wp->place.size.height);
	myCurrentWindowAlpha(windowList[i], &alpha);
	wp->hasAlpha = (alpha != 2);

	myCurrentWindowDepth(windowList[i], &depth);
	wp->colors = NXNumberOfColorComponents(NXColorSpaceFromDepth(depth));
	wp->depth = NXBPSFromDepth(depth);

	myCurrentOwner(windowList[i], &wp->context);
	myCurrentWindowLevel(windowList[i], &wp->level);
	
	if( wp->context == myContext )
	  wp->isMe = YES;
	else
	  wp->isMe = NO;

	wp->origOnScreen = wp->onScreenNow = NO;
  }

  return self;
}

// Build the on-screen list of windows.
- buildOnScreenList
{
  // Find the on-screen windows
  // with their order.
  PScountscreenlist(0, &numOnScreen);
  if( onScreenList )
	free(onScreenList);
  onScreenList = malloc(sizeof(int) * numOnScreen);
  PSscreenlist(0, numOnScreen, onScreenList);

  return self;
}

@end
